package com.appboy.ui.support; import android.content.Context; import android.graphics.drawable.Animatable; import android.net.Uri; import com.appboy.Appboy; import com.appboy.Constants; import com.appboy.configuration.AppboyConfigurationProvider; import com.appboy.support.AppboyLogger; import com.appboy.support.StringUtils; import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.controller.BaseControllerListener; import com.facebook.drawee.controller.ControllerListener; import com.facebook.drawee.interfaces.DraweeController; import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.imagepipeline.image.ImageInfo; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; /** * Since Fresco is a provided dependency, we have to check for its existence at runtime. To safeguard * against a major api change, we check for the existence of at least the imported classes used from * the Fresco library used in the UI project. */ public class FrescoLibraryUtils { private static final String TAG = String.format("%s.%s", Constants.APPBOY_LOG_TAG_PREFIX, FrescoLibraryUtils.class.getName()); private static boolean sCanUseFresco = false; private static boolean sCanUseFrescoSet = false; private static final String FILE_SCHEME = "file"; private static final String HTTP_SCHEME = "http"; private static final String HTTPS_SCHEME = "https"; private static final String[] USED_FRESCO_CLASSES = { "com.facebook.drawee.backends.pipeline.Fresco", "com.facebook.drawee.interfaces.DraweeController", "com.facebook.drawee.view.SimpleDraweeView", "com.facebook.drawee.backends.pipeline.Fresco", "com.facebook.drawee.controller.BaseControllerListener", "com.facebook.drawee.controller.ControllerListener", "com.facebook.imagepipeline.image.ImageInfo" }; /** * Returns the configuration value for Fresco enabled status. If the setting is not present, defaults to * true. */ private static boolean getIsFrescoEnabled(Context context) { AppboyConfigurationProvider appboyConfigurationProvider = new AppboyConfigurationProvider(context); return appboyConfigurationProvider.getIsFrescoLibraryUseEnabled(); } /** * Checks for the existence of the Facebook Fresco Image Library for use in the UI code. Also checks * for the provided xml setting. * * @return true if the fresco library is on the path AND if use of the fresco library is allowed * in the Appboy configuration settings. */ public static boolean canUseFresco(Context context) { if (sCanUseFrescoSet) { return sCanUseFresco; } context = context.getApplicationContext(); boolean isFrescoEnabledFromXml = getIsFrescoEnabled(context); if (!isFrescoEnabledFromXml) { sCanUseFresco = false; sCanUseFrescoSet = true; return false; } try { // Check for a subset of classes used from the Fresco library. ClassLoader staticClassLoader = FrescoLibraryUtils.class.getClassLoader(); sCanUseFresco = true; for (String classPath : USED_FRESCO_CLASSES) { if (Class.forName(classPath, false, staticClassLoader) == null) { // The class doesn't exist on the path sCanUseFresco = false; break; } } } catch (Exception e) { sCanUseFresco = false; } catch (NoClassDefFoundError ncd) { sCanUseFresco = false; } catch (Throwable t) { sCanUseFresco = false; } sCanUseFrescoSet = true; return sCanUseFresco; } public static void setDraweeControllerHelper(final SimpleDraweeView simpleDraweeView, final String imageUrl, final float aspectRatio, final boolean respectAspectRatio) { setDraweeControllerHelper(simpleDraweeView, imageUrl, aspectRatio, respectAspectRatio, null); } /** * Helper method for setting the controller on a simple Drawee View. By default, gif urls are set * to autoplay and tap to retry is on for all images. * * @param simpleDraweeView the fresco SimpleDraweeView in which to display the image * @param imageUrl the URL of the image resource * @param aspectRatio the desired aspect ratio of the image * @param respectAspectRatio if true, the aspect ratio of the image will be set to that of the value of aspectRatio. If false, the aspect ratio * will be set to that of the downloaded image dimensions. * @param controllerListener the controllerListener to use, or null if the default should be used. */ public static void setDraweeControllerHelper(final SimpleDraweeView simpleDraweeView, final String imageUrl, final float aspectRatio, final boolean respectAspectRatio, ControllerListener<ImageInfo> controllerListener) { if (StringUtils.isNullOrBlank(imageUrl)) { AppboyLogger.w(TAG, "The url set for the Drawee controller was null. Controller not set."); return; } if (simpleDraweeView == null) { AppboyLogger.w(TAG, "The SimpleDraweeView set for the Drawee controller was null. Controller not set."); return; } // Selectively cancel network loading based on the Appboy network state ImageRequest.RequestLevel requestLevel = Appboy.getOutboundNetworkRequestsOffline() ? ImageRequest.RequestLevel.DISK_CACHE : ImageRequest.RequestLevel.FULL_FETCH; AppboyLogger.d(TAG, "Setting Fresco image request level to: " + requestLevel); // Create a controller listener to listen for the dimensions of the image once set. Once // we get the dimensions, set the aspect ratio of the image based on respectAspectRatio. if (controllerListener == null) { controllerListener = new BaseControllerListener<ImageInfo>() { @Override public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) { if (imageInfo == null) { return; } final float imageAspectRatio; if (respectAspectRatio) { imageAspectRatio = aspectRatio; } else { // Get the image aspect ratio from the imageInfo imageAspectRatio = imageInfo.getWidth() / imageInfo.getHeight(); } // Set this aspect ratio on the drawee itself on the UI thread simpleDraweeView.post(new Runnable() { @Override public void run() { simpleDraweeView.setAspectRatio(imageAspectRatio); } }); } }; } // If the Fresco singleton is shutdown prematurely via Fresco.shutdown() then the Fresco.newDraweeControllerBuilder() // will throw a NPE. We catch this below to safeguard against this gracefully. try { Uri uri = getFrescoUri(imageUrl); ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri) .setLowestPermittedRequestLevel(requestLevel) .build(); DraweeController controller = Fresco.newDraweeControllerBuilder() .setUri(uri) .setAutoPlayAnimations(true) .setTapToRetryEnabled(true) .setControllerListener(controllerListener) .setImageRequest(request) .build(); simpleDraweeView.setController(controller); } catch (NullPointerException e) { AppboyLogger.e(TAG, "Fresco controller builder could not be retrieved. Fresco most likely prematurely shutdown.", e); } catch (Exception e) { AppboyLogger.e(TAG, "Fresco controller builder could not be retrieved. Fresco most likely prematurely shutdown.", e); } } static Uri getFrescoUri(String uriString) { Uri uri = Uri.parse(uriString); if (StringUtils.isNullOrBlank(uri.getScheme())) { return Uri.parse(FILE_SCHEME + "://" + uriString); } return uri; } }